<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>模拟光照让3D场景更逼真（上）-聚光灯</title>
    <style>
        canvas{
            display: block;
            border: 1px solid #ccc;
        }
    </style>
</head>
<body>
    <canvas width="512" height="512"></canvas>

    <script type="module">
        import { Renderer, Camera, Transform, Box, Sphere, Cylinder, Torus, Program, Mesh } from 'https://unpkg.com/ogl';

        /**
         * OGL用法
         * 1.创建renderer对象
         * 2.创建相机
         * 3.创建场景
         * 4.创建几何体对象
         * 5.创建webgl程序
         * 6.设置网格对象
         * 7.完成渲染
         * */ 

        const canvas = document.querySelector("canvas");
        //  1.创建renderer对象
        const renderer = new Renderer({
            canvas,
            width:512,
            height:512,
        });
        const gl = renderer.gl;
        gl.clearColor(1, 1, 1, 1);

        // 2.创建相机
        const camera = new Camera(gl, {
            fov:35
        });
        // 默认透视投影
        camera.position.set(0, 1, 7);
        camera.lookAt([0,0,0]);

        // 3.创建场景
        const scene = new Transform();

        // 4.创建几何体对象
        // 立方体
        const boxGeometry = new Box(gl);
        // 球体
        const sphereGeometry = new Sphere(gl);
        // 圆柱体
        const cylinderGeometry = new Cylinder(gl);
        // 圆环体
        const torusGeometry = new Torus(gl);

        // 5.创建webgl程序
        const vertex = `
            attribute vec3 position;
            attribute vec3 normal;

            uniform mat4 projectionMatrix;
            uniform mat4 modelViewMatrix;
            uniform mat3 normalMatrix;

            varying vec3 vNormal;
            varying vec4 vPos;
            
            // 点光源位置
            const vec3 lightPosition = vec3(1.0, 0.0, 0.0);
            
            void main() {
                vPos = modelViewMatrix * vec4(position, 1.0);

                vNormal = normalize(normalMatrix * normal);

                gl_Position = projectionMatrix * vPos;
            }
        `;
        const fragment = `
            #ifdef GL_ES
                precision mediump float;
            #endif

            uniform mat4 viewMatrix;

            varying vec3 vNormal;
            varying vec4 vPos;

            uniform vec3 ambientLight;
            uniform vec3 materialReflection;

            uniform vec3 spotLightPosition;
            uniform vec3 spotLightColor;
            uniform vec3 spotLightDirection;
            uniform float spotLightAngle;
            uniform vec3 spotLightDecayFator;
            
            void main() {
                // 顶点形成的向量
                vec4 vDir = viewMatrix * vec4(spotLightPosition, 1.0) - vPos;

                // 到顶点的距离
                float dis = length(vDir);
                // 计算衰减
                float decay = min(1.0, 1.0 / (spotLightDecayFator.x * pow(dis, 2.0) + spotLightDecayFator.y * dis + spotLightDecayFator.z));

                // 判断角度范围
                float c = cos(spotLightAngle);
                vec3 dir = (viewMatrix * vec4(spotLightDirection, 0.0)).xyz;
                float d = step(c, dot(normalize(-vDir.xyz), normalize(dir)));

                // 光源强度
                float vCos = max(dot(vNormal, normalize(vDir.xyz)), 0.0);
                // 计算漫反射
                vec3 diffuse = d * decay * spotLightColor * vCos;


                // 环境光
                vec3 lightColor = (ambientLight + diffuse) * materialReflection;

                gl_FragColor.rgb = lightColor;
                gl_FragColor.a = 1.0;
            }
        `;
        const ambientLight = {value:[0.5, 0.5, 0.5]};
        const light = {
            // 平行光
            // directionalLight:{value:[1,0,0]},
            // directionalLightColor:{value:[1,1,1]},
            // pointLightPosition:{value:[-1,1,0.5]},
            // pointLightColor:{value:[1,1,1]},
            // pointLightDecayFator:{value:[0.05,0,1]},
            spotLightPosition:{value:[3,3,0]},
            spotLightColor:{value:[1,1,1]},
            spotLightDirection:{value:[-1,-1,0]},
            spotLightAngle:{value:Math.PI / 12},
            spotLightDecayFator:{value:[0.05,0,1]},
        }
        const program1 = new Program(gl, {
            vertex,
            fragment,
            uniforms:{
                ambientLight,
                materialReflection:{value:[0.5,0,0.5]},
                ...light
            }
        });
        const program2 = new Program(gl, {
            vertex,
            fragment,
            uniforms:{
                ambientLight,
                materialReflection:{value:[0.5,0.5,0]},
                ...light
            }
        });
        const program3 = new Program(gl, {
            vertex,
            fragment,
            uniforms:{
                ambientLight,
                materialReflection:{value:[0,0.5,0]},
                ...light
            }
        });
        const program4 = new Program(gl, {
            vertex,
            fragment,
            uniforms:{
                ambientLight,
                materialReflection:{value:[0,0,0.5]},
                ...light
            }
        });

        // 6.设置网格对象
        const boxMesh = new Mesh(gl, {geometry:boxGeometry, program:program1});
        boxMesh.position.set(0, 1.3, 0);
        boxMesh.setParent(scene);

        const sphereMesh = new Mesh(gl, {geometry:sphereGeometry, program:program2});
        sphereMesh.position.set(1.3, 0, 0);
        sphereMesh.setParent(scene);

        const cylinderMesh = new Mesh(gl, {geometry:cylinderGeometry, program:program3});
        cylinderMesh.position.set(0, -1.3, 0);
        cylinderMesh.setParent(scene);

        const torusMesh = new Mesh(gl, {geometry:torusGeometry, program:program4});
        torusMesh.position.set(-1.3, 0, 0);
        torusMesh.setParent(scene);

        // 7. 完成渲染

        function update(t){
            boxMesh.rotation.y -= 0.01;
            sphereMesh.rotation.y -= 0.02;
            cylinderMesh.rotation.y -= 0.03;
            torusMesh.rotation.y -= 0.04;
            renderer.render({scene, camera});
            requestAnimationFrame(update);
        }
        update();
    </script>
</body>
</html>